/*
 * Copyright 2008 Lucas Tan.
 * This copyright notice must always remain intact whenever you use or distribute 
 * the source code contained in this file.
 * Any usage of any code in this file, either in derived work or
 * through the use of external linkage,
 * must always be credited with the author's name.
 * 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 * */
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Text.RegularExpressions;
using System.Diagnostics;
using PassKeeper.Properties;
using PassKeeper.Data;

namespace PassKeeper.Gui
{
	public partial class Main : Form
	{
		[Flags]
		private enum Buttons : int
		{
			New = 1,
			Open = 2,
			Save = 4,
			Close = 8,
			Apply = 16,
			SetDefault = 32,
			None = 0,
			All = (New | Open | Save | Close | Apply | SetDefault)
		}

		private enum Panels 
		{
			Default,
			EnterPassword,
			NewPassword,
			Database
		}

		private enum Phase 
		{
			Default,
			EnterPassword,
			NewPassword,
			DatabaseOpened
		}

		private class AccountSorter : IComparer
		{
			public AccountSorter()
			{
				// do nothing.
			}

			#region IComparer Members

			public int Compare(object x, object y)
			{
				TreeNode node1 = (TreeNode) x;
				TreeNode node2 = (TreeNode) y;

				/*Localized string comparer.*/
				return StringComparer.CurrentCulture.Compare(node1.Text, node2.Text);
			}

			#endregion
		}

		private bool _isCurAccountChanged;
		private bool _isCurDatabaseChanged;
		private bool _disableTextChanged;

		private string _curFullPath;
		private Database _curDatabase;
		private TreeNode _prevNode;
		private int _accountCount;
		
		private Phase _curPhase;
		private Panel _prevPanelObject;
		
		private Settings _settings;
		private bool _settingsLoaded;
		private bool _isFirstShown;

	//	private TextBox _txtPass2;

		private void LoadToolStripImage(ToolStripButton btn, string id)
		{
			MemoryStream s = new MemoryStream((byte[])Resources.ResourceManager.GetObject(id));
			btn.Image = new Bitmap(s);
			s.Dispose();
		}

		public Main()
		{
			/*The first statement to create the controls
			 before we start using them.*/
			InitializeComponent();

			
			/*Must initialize the values to the default first.*/
			_settings = Settings.Default;
			_isFirstShown = true;
			_settingsLoaded = false;
			_curPhase = Phase.Default;

			/*Such settings must be set during runtime
			 ; They would make designing the controls
			 difficult if set during design time.*/
			tlpMain.Dock = DockStyle.Fill;
			pnlContainer.Dock = DockStyle.Fill;

			/*This statement is very important in the saving/loading of splitter distance
			 setting.*/
			sptDatabase.FixedPanel = FixedPanel.Panel1;

			tvAccounts.TreeViewNodeSorter = new AccountSorter();
			
			this.Text = Util.GetResourceString("Application_Name");
			this.Icon = Resources.PassKeeper;
		
			/*We have to manually load the images because the resource compiler
			 auto-converts PNG images to BMP images, which take up a lot
			 of space.*/
			LoadToolStripImage(tsbNew, "ImageNew");
			LoadToolStripImage(tsbOpen, "ImageOpen");
			LoadToolStripImage(tsbApply, "ImageApply");
			LoadToolStripImage(tsbClose, "ImageClose");
			LoadToolStripImage(tsbSetDefault, "ImageDefault");
			LoadToolStripImage(tsbSave, "ImageSave");
			LoadToolStripImage(tsbAbout, "ImageAbout");

			lblChangePasswordInstruction.Text = Util.FormatResourceString(
				"Label_Change_Password",
				Util.Format(Settings.DatabasePasswordMinLength), Util.Format(Settings.DatabasePasswordMaxLength)
			);
			lblNewPasswordInstruction.Text = Util.FormatResourceString(
				"Label_Select_New_Password",
				Util.Format(Settings.DatabasePasswordMinLength), Util.Format(Settings.DatabasePasswordMaxLength)
				);

			CloseDatabase();
		}

		~Main()
		{
			Dispose(_curDatabase);
			_curDatabase = null;
			_prevNode = null;
			_prevPanelObject = null;
		}

		private void Dispose(object obj)
		{
			IDisposable disp = obj as IDisposable;

			if (disp != null)
			{
				disp.Dispose();
			}
		}

		#region Main form events

		private void Main_Shown(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, this));

			/* The reason why the database opening code is put here
			 is to enable showing of any error message with the main app window
			 being visible.*/

			if (_isFirstShown)
			{
				_isFirstShown = false;
				this.WindowState = (_settings.MainWindowMaximized ? FormWindowState.Maximized : FormWindowState.Normal);

				/* check command line for file name to open . 
				 The first token is the executable file name, so we skip it.*/
				string[] tokens=Environment.GetCommandLineArgs();

				if (tokens != null && tokens.Length > 1)
				{
					if (File.Exists(tokens[1]))
					{
						Open(tokens[1]);
						return;
					}
				}

				string databaseFullPath = _settings.DefaultDatabaseFullPath;

				if (string.IsNullOrEmpty(databaseFullPath))
				{
					return;
				}
				else if (File.Exists(databaseFullPath))
				{
					Open(databaseFullPath);
				}
				else
				{
					ShowErrorMessage(
						Util.FormatResourceString("File_Not_Found", databaseFullPath)
					);
				}
			}
		}

		private void Main_StyleChanged(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, this));
			
			/*We reach here when the user has changed the theme of the system
			 * or the form is first shown.*/

			/*Change the dialog font of this form to match that of the
			 rest of the system.*/
			Util.SetFont(this);

			/*Change label text to bold font.*/
			Font f = new Font(lblName.Font, FontStyle.Bold);
			lblName.Font =
				lnkUri.Font =
				lblUsername.Font =
				lblPassword.Font =
				lblEmailAddress.Font =
				lblRemarks.Font = f;
			
			/*Sets the minimum size.*/
			this.MinimumSize = new Size(

				tsMain.PreferredSize.Width +
					/*Convert client width to non-client (system) width*/
					(this.Size.Width - this.ClientSize.Width),

				this.MinimumSize.Height
			);

			if (!_settingsLoaded)
			{
				/*Only load the settings after we have done all the adjustments.*/
				LoadControlSettings();
				_settingsLoaded = true;
			}
		}

		private void Main_FormClosing(object sender, FormClosingEventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, this));

			if (CheckForChanges())
			{
				_settings.MainWindowMaximized = (this.WindowState == FormWindowState.Maximized);
				_settings.MainWindowBounds = (this.RestoreBounds);
				_settings.MainWindowSplitterDistance = this.sptDatabase.SplitterDistance;
				_settings.Save();
				e.Cancel = false;
			}
			else
			{
				e.Cancel = true;
			}
		}

		#endregion

		#region Panel methods

		private Panel GetPanel(Panels panel)
		{
			switch (panel)
			{
			case Panels.Default:
				return pnlDefault;

			case Panels.Database:
				return pnlDatabase;

			case Panels.EnterPassword:
				return pnlEnterPassword;

			case Panels.NewPassword:
				return pnlNewPassword;

			default:

				/*Shouldn't reach here.*/
				throw new Exception("Unknown panel");
			}
		}

		private void ShowPanel(Panels panel,Phase newPhase)
		{
			ShowPanel(panel);

			_curPhase = newPhase;
		}

		private void ShowPanel(Panels panel)
		{
			Panel panelObject = GetPanel(panel);

			this.AcceptButton = null;
			this.CancelButton = null;

			panelObject.Dock = DockStyle.Fill;
			panelObject.Margin = new Padding(0);
			panelObject.BringToFront();
			panelObject.Visible = true;

			if (panel == Panels.NewPassword)
			{
				txtNewPassword1.Text = string.Empty;
				txtNewPassword2.Text = string.Empty;
				txtNewPassword1.MaxLength = Settings.DatabasePasswordMaxLength;
				txtNewPassword2.MaxLength = Settings.DatabasePasswordMaxLength;
			}

			/*Hide the previous panel after only we have shown the new
			 one so that there won't be a flicker.*/
			if (_prevPanelObject != null)
			{
				_prevPanelObject.Visible = false;
			}

			_prevPanelObject = panelObject;

			/*The default buttons to enable.*/
			Buttons buttonsToEnable=Buttons.None;

			switch (panel)
			{
			case Panels.Database:

				tabDatabase.SelectedTab = tbpAccounts;
				txtName.Focus();
				buttonsToEnable = (Buttons.New | Buttons.Open | Buttons.SetDefault | Buttons.Close);
				break;

			case Panels.EnterPassword:

				chkCheckXmlSig.Checked = true;
				txtEnterPassword.Focus();
				this.AcceptButton = btnEnterPasswordOK;
				this.CancelButton = btnEnterPasswordCancel;
				buttonsToEnable = (Buttons.New | Buttons.Open | Buttons.Close);
				break;

			case Panels.NewPassword:

				txtNewPassword1.Focus();
				this.AcceptButton = btnNewPasswordOK;
				this.CancelButton = btnNewPasswordCancel;
				buttonsToEnable = (Buttons.Open);
				break;

			case Panels.Default:

				buttonsToEnable = (Buttons.New | Buttons.Open);
				break;
			}

			EnableButtons(Buttons.All, false);
			EnableButtons(buttonsToEnable, true);
		}

		#endregion

		#region Data validation methods

		private bool IsValidDatabasePassword(string password)
		{
			if (password == null)
			{
				throw new ArgumentNullException("password");
			}

			return Settings.DatabasePasswordValidator.IsMatch(password);
		}

		private bool TryCreateUri(string uri, out Uri uriObj)
		{
			if (uri == null)
			{
				throw new ArgumentNullException("uri");
			}

			uriObj = null;

			try
			{
				uriObj = new Uri(uri, UriKind.Absolute);
			}
			catch
			{/*do nothing.*/
			}

			return uriObj != null;
		}

		#endregion

		#region Database/accoutn changes handlers

		private void SetAccountChanged(bool changed)
		{
			EnableButtons(Buttons.Apply , changed);

			if (changed)
			{
				/*Enable the save button as well so as to allow
				 the user to save the account without first clicking
				 the apply button.*/
				EnableButtons(Buttons.Save, true);
			}

			else if (!changed && !_isCurDatabaseChanged)
			{
				EnableButtons(Buttons.Save, false);
			}

			_isCurAccountChanged = changed;
		}

		private void SetDatabaseChanged(bool changed)
		{
			EnableButtons(Buttons.Save, changed);
			_isCurDatabaseChanged = changed;
		}

		private bool ApplyAccountChanges()
		{
			if (!_isCurAccountChanged)
			{
				return true;
			}

			string name = txtName.Text;
			string uri = txtUri.Text;
			string username = txtUsername.Text;
			string password = txtPassword.Text;
			string emailAddress = txtEmailAddress.Text;
			string remarks = txtRemarks.Text;

			Uri uriObj=null;

			name = name.Trim();
			uri = uri.Trim();
			emailAddress = emailAddress.Trim();

			_disableTextChanged = true;
			txtName.Text = name;
			txtUri.Text = uri;
			txtEmailAddress.Text = emailAddress;
			_disableTextChanged = false;

			if (string.IsNullOrEmpty(name))
			{
				ShowInfoMessage(
					Util.GetResourceString("Enter_A_Name")
				);
				txtName.SelectAll();
				txtName.Focus();
				return false;
			}

			TreeNode curNode = tvAccounts.SelectedNode;
			Account curAccount = (Account) curNode.Tag;
			string curAccountName = curAccount.Name;
			AccountCollection curAccountCollection = (AccountCollection) curAccount.Parent;

			bool rename = ! AccountCollection.NameEqualityComparer.Equals(name,curAccountName);

			if (
				/*Check whether the new name already exists.*/
				rename && curAccountCollection.Contains(name)
			)
			{
				ShowInfoMessage(
					Util.FormatResourceString("Account_Already_Exists", name)
				);
				txtName.SelectAll();
				txtName.Focus();
				return false;
			}

			if (!string.IsNullOrEmpty(uri) && !TryCreateUri(uri, out uriObj))
			{
				ShowInfoMessage(
					Util.GetResourceString("Invalid_Uri")
				);
				txtUri.SelectAll();
				txtUri.Focus();
				return false;
			}

			/*We have to put this two notification call here because of
			 'tvAccounts.Sort()' call below.*/
			SetAccountChanged(false);
			SetDatabaseChanged(true);
			
			if (rename)
			{
				curAccount.Name = name;
				curNode.Text = name;
				
				/*The treeview control is seriously screwed up.
				 The sort method itself will modify the selection and hence
				 trigger the 'Before/After' select events.
				 
				 And it doesn't have an auto-sort feature.*/
				tvAccounts.Sort();
			}

			curAccount.Uri = uriObj;
			curAccount.Username = username;
			curAccount.Password = password;
			curAccount.EmailAddress = emailAddress;
			curAccount.Remarks = remarks;
	
			tvAccounts.SelectedNode = curNode;
			return true;
		}

		private bool CheckForAccountChanges()
		{
			if (!_isCurAccountChanged)
			{
				return true;
			}

			if (Ask(Util.GetResourceString("Want_Apply_Account_Changes")))
			{
				return ApplyAccountChanges();
			}

			/*Discard the changes.*/
			SetAccountChanged(false);
			return true;
		}

		private bool SaveDatabase()
		{
			if (_curFullPath == null)
			{
				_curFullPath = BrowseForSave();

				if (_curFullPath == null)
				{
					/*The user has cancelled the "Save As" dialog.*/
					return false;
				}
			}

			try
			{
				_curDatabase.Save(_curFullPath);
			}
			catch (Exception e)
			{
				ExceptionBox.Show (this, Util.GetResourceString ("Database_Not_Saved"), e);
				return false;
			}

			string backupFileName = _curFullPath + ".bak";

			try
			{
				File.Copy(_curFullPath, backupFileName, true);
			}
			catch (Exception e)
			{
				ExceptionBox.Show(this, Util.FormatResourceString("Database_Not_Backup", backupFileName), e);
				return false;
			}

			SetStatusMessage(_curFullPath);
			SetAccountChanged(false);
			SetDatabaseChanged(false);
			return true;
		}

		/// <summary>
		/// Usually called when the main form is closing or the user
		/// is opening a new database.
		/// </summary>
		/// <returns></returns>
		private bool CheckForChanges()
		{
			if ( _isCurAccountChanged)
			{
				if (Ask(Util.GetResourceString("Want_Apply_Account_Changes_And_Save")))
				{
					if (!ApplyAccountChanges())
					{
						return false;
					}

					return SaveDatabase();
				}

				SetAccountChanged(false);
			}
			else if (_isCurDatabaseChanged && !_isCurAccountChanged)
			{
				if (Ask(Util.GetResourceString("Want_Save_Database")))
				{
					return SaveDatabase();
				}
			}
			
			/*We reach here when the user wishes to discard the changes
			 or both the database and account ain't modified.*/
			
			SetDatabaseChanged(false);
			return true;
		}

		#endregion

		#region Misc functions

		/// <summary>
		/// Load visual settings for the controls on the form.
		/// </summary>
		private void LoadControlSettings()
		{
			/*
			 * Do not change the order in which the settings are loaded.
			 * 
			 * */

			/*Wrap in a try-catch block in case the setting is corrupted.*/
			try
			{
				Rectangle bounds = _settings.MainWindowBounds;

				//TODO: check whether bounds is out of screen.

				this.Bounds = bounds;
			}
			catch
			{
				/*Accept the default window size and center to screen.*/
				this.CenterToScreen();
			}

			try
			{
				this.WindowState = (
					_settings.MainWindowMaximized ?
					FormWindowState.Maximized :
					FormWindowState.Normal
				);
			}
			catch
			{
				/*do nothing.*/
			}

			try
			{
				if (_settings.MainWindowSplitterDistance != 0)
				{
					sptDatabase.SplitterDistance = _settings.MainWindowSplitterDistance;
				}
			}
			catch
			{	/*do nothing.*/
			}
		}

		private string BrowseForSave()
		{
			DialogResult result = dlgSaveFile.ShowDialog(this);

			if (result == DialogResult.OK)
			{
				/*Assume: file name is absolute.*/
				return dlgSaveFile.FileName;
			}

			return null;
		}

		private string BrowseForOpen()
		{
			DialogResult result = dlgOpenFile.ShowDialog(this);

			if (result == DialogResult.OK)
			{
				return dlgOpenFile.FileName;
			}

			return null;
		}

		#endregion

		#region Open/close database

		private bool CloseDatabase()
		{
			if (_curPhase == Phase.DatabaseOpened)
			{
				if (!CheckForChanges())
				{
					return false;
				}
			}

			Dispose(_curDatabase);
			_curDatabase = null; /*Set to null so that we don't accidentally dispose
								  it twice.*/
		
			_curFullPath = null;
			_prevNode = null;
			_isCurAccountChanged = false;
			_isCurDatabaseChanged = false;
		
			SetStatusMessage(string.Empty);

			/*Show the default panel to indicate
					 we have closed the file.*/
			ShowPanel(Panels.Default, Phase.Default);

			return true;
		}

		private void Open(string fileName)
		{
			if (fileName==null)
			{
				throw new ArgumentNullException("fileName");
			}

			_curFullPath = fileName;

			SetStatusMessage(Util.FormatResourceString("Opening_File", fileName));

			txtEnterPassword.Text = string.Empty;
			/*Don't set the max length so that we can remain future compatible.*/

			ShowPanel(Panels.EnterPassword, Phase.EnterPassword);
		}

		#endregion

		#region Database Display methods

		private int GetTotalCount(Account account)
		{
			int count = 1;

			foreach (Account a in account.Accounts)
			{
				count += GetTotalCount(a);
			}

			return count;
		}

		private void UpdateAccountCount(int delta)
		{
			_accountCount += delta;

			if (_accountCount <= 1)
			{
				lblAccountCount.Text = Util.FormatResourceString(
					"Account_Count", Util.Format(_accountCount)
				);
			}
			else
			{
				lblAccountCount.Text = Util.FormatResourceString(
					   "Account_Count_Plural", Util.Format(_accountCount)
				   );
			}
		}

		private int DisplayAccountCollection(TreeNodeCollection parent, AccountCollection accounts)
		{
			int count = 0;

			foreach (Account account in accounts)
			{
				TreeNode node = new TreeNode(account.Name);
				node.Tag = account;
				parent.Add(node);

				//todo: prevent stack overflow
				count += 1 + DisplayAccountCollection(node.Nodes, account.Accounts);
			}

			return count;
		}

		private void DisplayCurrentNode()
		{
			_disableTextChanged = true;

			TreeNode node = _prevNode;
	
			if (node == null)
			{
				txtName.Text = string.Empty;
				txtUri.Text = string.Empty;
				txtUsername.Text = string.Empty;
				txtPassword.Text = string.Empty;
				txtEmailAddress.Text = string.Empty;
				txtRemarks.Text = string.Empty;

				txtName.ReadOnly =
					txtUri.ReadOnly =
					txtUsername.ReadOnly =
					txtPassword.ReadOnly =
					txtEmailAddress.ReadOnly =
					txtRemarks.ReadOnly = true;

				lnkUri.Enabled =
					lblName.Enabled =
					lblUsername.Enabled =
					lblPassword.Enabled =
					lblRemarks.Enabled =
					lblEmailAddress.Enabled = false;

				btnAccountRemove.Enabled = false;
			}
			else
			{
				txtName.ReadOnly =
					txtUri.ReadOnly =
					txtUsername.ReadOnly =
					txtPassword.ReadOnly =
					txtEmailAddress.ReadOnly =
					txtRemarks.ReadOnly =false;

				lnkUri.Enabled =
				lblName.Enabled =
				lblUsername.Enabled =
				lblPassword.Enabled =
				lblRemarks.Enabled =
				lblEmailAddress.Enabled = true;

				Account curAccount = (Account) node.Tag;
				txtName.Text = curAccount.Name;
				txtUri.Text = (curAccount.Uri != null ? curAccount.Uri.OriginalString : string.Empty);
				txtUsername.Text = (curAccount.Username != null ? curAccount.Username : string.Empty);
				txtPassword.Text = (curAccount.Password != null ? curAccount.Password : string.Empty);
				txtEmailAddress.Text = (curAccount.EmailAddress != null ? curAccount.EmailAddress : string.Empty);
				txtRemarks.Text = (curAccount.Remarks != null ? curAccount.Remarks : string.Empty);

				btnAccountRemove.Enabled = true;
			}

			_disableTextChanged = false;
		}

		private void DisplayDatabase()
		{
			tvAccounts.SelectedNode = null;
			tvAccounts.Nodes.Clear();
			txtChangePassword1.Text = string.Empty;
			txtChangePassword2.Text = string.Empty;
			txtChangePassword1.MaxLength = Settings.DatabasePasswordMaxLength;
			txtChangePassword2.MaxLength = Settings.DatabasePasswordMaxLength;

			SetAccountChanged(false);
			SetDatabaseChanged(false);

			_accountCount = 0;
			UpdateAccountCount(DisplayAccountCollection(tvAccounts.Nodes, _curDatabase.Accounts));

			if (_curFullPath != null)
			{
				SetStatusMessage(_curFullPath);
			}
			else
			{
				SetStatusMessage(Util.GetResourceString("Database_Not_Saved_Yet"));
			}

			tvAccounts.ExpandAll();
			tvAccounts.SelectedNode = null;
			DisplayCurrentNode();

			if (tvAccounts.Nodes.Count > 0)
			{
				tvAccounts.Nodes[0].EnsureVisible();
			}

			ShowPanel(Panels.Database, Phase.DatabaseOpened);
		}

		#endregion

		#region pnlEnterPassword event handlers

		private void btnEnterPasswordOK_Click(object sender, EventArgs e)
		{
			if (_curPhase == Phase.EnterPassword)
			{
				string password = txtEnterPassword.Text;

				/*Note that we don't validate the password in order
				 to remain forward compatible.
				 In the future, the restrictions on password may change, such as
				 * the max length etc.
				 */

				/*So we just check for emptiness.*/
				if (string.IsNullOrEmpty(password))
				{
					ShowInfoMessage(Util.GetResourceString("Enter_Password"));
					
					txtEnterPassword.Focus();
					return;
				}

				if (_curDatabase != null)
				{
					Dispose(_curDatabase);
				}

				_curDatabase = new Database(password);
				bool ok = false;

				try
				{
					_curDatabase.Load(_curFullPath,chkCheckXmlSig.Checked);
					ok = true;
				}
				catch (SignatureVerificationException sve)
				{
					if (sve.Reason == SignatureVerificationFailureReason.InvalidSignature)
					{
						ShowErrorMessage(Util.GetResourceString("Invalid_Password_Or_File_Modified"));

					}
					else
					{
						ExceptionBox.Show(this, Util.FormatResourceString("Corrupted_File", _curFullPath), sve);
					}
				}
				catch (ParseException pe)
				{
					ExceptionBox.Show(this, Util.FormatResourceString("Corrupted_File", _curFullPath), pe);
				}
				catch (Exception ex)
				{
					ExceptionBox.Show(this, Util.GetResourceString("Cannot_Open_File"), ex);
				}

				if (!ok)
				{
					Dispose(_curDatabase);
					_curDatabase = null;
					return;
				}
				else
				{
					DisplayDatabase();
				}
			}
		}

		private void btnEnterPasswordCancel_Click(object sender, EventArgs e)
		{/*done*/

			if (_curPhase == Phase.EnterPassword)
			{
				CloseDatabase();
			}
		}

		#endregion

		#region Status bar methods

		private void SetStatusMessage(string message)
		{/*done*/

			if (message == null)
			{
				throw new ArgumentNullException("message");
			}

			lblStatusMessage.Text = message;
		}

		#endregion

		#region Dialog methods

		private bool Ask(string message, string caption, MessageBoxIcon icon)
		{
			DialogResult result =
				MessageBox.Show(
					message,
					caption,
					MessageBoxButtons.YesNo,
					icon
				);

			if (result != DialogResult.Yes && 
				result != DialogResult.No)
			{
				throw new Exception("unexpected dialog result");
			}

			return result == DialogResult.Yes;
		}

		private bool Ask(string message, MessageBoxIcon icon)
		{
			return Ask(
				message,
				Util.GetResourceString("Prompt_Message_Caption"),
				icon
				);
		}

		private bool Ask(string message)
		{
			return Ask(message, MessageBoxIcon.Question);
		}

		private void ShowMessage(string message, string caption, bool modeless, MessageBoxIcon icon)
		{
			if (modeless)
			{
				MessageBox.Show(
					message,
					caption,
					MessageBoxButtons.OK,
					icon
				);
			}
			else
			{
				MessageBox.Show(
					this,
					message,
					caption,
					MessageBoxButtons.OK,
					icon
				);
			}
		}

		private void ShowInfoMessage(string message)
		{
			ShowMessage(message, Util.GetResourceString("Application_Name"), false, MessageBoxIcon.Information);
		}

		private void ShowErrorMessage(string message)
		{
			ShowErrorMessage(message, false);
		}

		private void ShowErrorMessage(string message,string caption, bool modeless)
		{
			ShowMessage(message, caption, modeless, MessageBoxIcon.Error);
		}

		private void ShowErrorMessage(string message, bool modeless)
		{
			ShowErrorMessage(message,Util.GetResourceString("Error_Message_Caption"), modeless);
		}

		#endregion

		#region pnlDatabase event handlers and methods

		private void txtPassword_Enter(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, txtPassword));

			//if (_txtPass2 == null)
			//{
			//    /* Creates a new password box to solve the 
			//     * "unable to tab through the password box" bug */
			//    _txtPass2 = new TextBox();
			//    _txtPass2.Location = txtPassword.Location;
			//    _txtPass2.Size = txtPassword.Size;
			//    _txtPass2.Font = txtPassword.Font;
			//    _txtPass2.Anchor = txtPassword.Anchor;
			//    _txtPass2.Text = txtPassword.Text;
			//    _txtPass2.Margin = txtPassword.Margin;
			//    _txtPass2.MaximumSize = txtPassword.MaximumSize;
			//    _txtPass2.MinimumSize = txtPassword.MinimumSize;
			//    _txtPass2.UseSystemPasswordChar = false;
			//    TableLayoutPanelCellPosition cellPos = tlpFields.GetCellPosition(txtPassword);
			//    tlpFields.Controls.Remove(txtPassword);
			//    tlpFields.Controls.Add(_txtPass2, cellPos.Column, cellPos.Row);
			////	txtPassword.Parent.Controls.Add(_txtPass2);
			//    _txtPass2.Leave += new EventHandler(delegate{
			//        if (_txtPass2 != null)
			//        {
			//            cellPos = tlpFields.GetCellPosition(_txtPass2);
			//            tlpFields.Controls.Remove(_txtPass2);
			//            tlpFields.Controls.Add(txtPassword, cellPos.Column, cellPos.Row);
			//            _txtPass2 = null;
			//        }
			//    });
			//    _txtPass2.Focus();
			//}

			txtPassword.UseSystemPasswordChar = false;
		}

		private void txtPassword_Leave(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, txtPassword));

			//if (_txtPass2 != null)
			//{
			//    TableLayoutPanelCellPosition cellPos = tlpFields.GetCellPosition(_txtPass2);
			//    tlpFields.Controls.Remove(_txtPass2);
			//    tlpFields.Controls.Add(txtPassword, cellPos.Column, cellPos.Row);
			//    _txtPass2 = null;
			//}

			/*Mask the password again when the control becomes inactive.*/
			if (txtPassword.Tag == null)
			{
				txtPassword.Tag = new object();
				Timer.SetTimeOut(50, delegate
				{
					txtPassword.UseSystemPasswordChar = true;
					txtPassword.Tag = null;
				});
			}
		}

		private void TextFields_TextChanged(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, txtName) ||
				object.ReferenceEquals(sender, txtPassword) ||
				object.ReferenceEquals(sender, txtUri) ||
				object.ReferenceEquals(sender, txtUsername) ||
				object.ReferenceEquals(sender, txtEmailAddress) ||
				object.ReferenceEquals(sender, txtRemarks)
			);

			if (_curPhase == Phase.DatabaseOpened)
			{
				/*We only want to capture user triggered event, not
				 programmatically trigerred event.*/
				if (!_disableTextChanged)
				{
					SetAccountChanged(true);
				}
			}
		}

		private void tvAccounts_BeforeSelect(object sender, TreeViewCancelEventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tvAccounts));

			if (_curPhase == Phase.DatabaseOpened)
			{
				if (!CheckForAccountChanges())
				{
					e.Cancel = true;
					return;
				}

				e.Cancel = false;
			}
		}

		private void tvAccounts_AfterSelect(object sender, TreeViewEventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tvAccounts));
		
			if (_curPhase == Phase.DatabaseOpened)
			{
				if (object.ReferenceEquals(_prevNode, e.Node))
				{
					return;
				}

				_prevNode = e.Node;
				DisplayCurrentNode();
			}
		}

		private void lnkURI_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, lnkUri));

			string uriStr = txtUri.Text;
			uriStr = uriStr.Trim();

			_disableTextChanged = true;
			txtUri.Text = uriStr;
			_disableTextChanged = false;

			Uri uriObj = null;
			if (string.IsNullOrEmpty(uriStr) || !TryCreateUri(uriStr, out uriObj))
			{
				ShowInfoMessage(Util.GetResourceString("Invalid_Uri"));
				return;
			}

			if (
				uriObj.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase)
				|| uriObj.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)
				|| uriObj.Scheme.Equals(Uri.UriSchemeFtp, StringComparison.OrdinalIgnoreCase)
				)
			{
				Util.Browse(uriObj);
			}
			else if (
				uriObj.Scheme.Equals(Uri.UriSchemeFile, StringComparison.OrdinalIgnoreCase)
				
			)
			{
				if (Ask(Util.GetResourceString("Unsafe_File_Url"), MessageBoxIcon.Warning))
				{
					Util.Browse(uriObj);
				}
			}
			else if (Ask(Util.GetResourceString("Unsafe_Url")))
			{
				Util.Browse(uriObj);
			}
		}

		private void btnAccountAdd_Click(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, btnAccountAdd));

			if (_curPhase == Phase.DatabaseOpened)
			{
				if (!CheckForAccountChanges())
				{
					return;
				}

				AccountCollection accounts = null;
				TreeNodeCollection nodes = null;

				if (tvAccounts.SelectedNode == null)
				{
					accounts = _curDatabase.Accounts;
					nodes = tvAccounts.Nodes;
				}
				else
				{
					accounts = ((Account) tvAccounts.SelectedNode.Tag).Accounts;
					nodes = tvAccounts.SelectedNode.Nodes;
				}
				
				string newNodeName = null;
				int num = _accountCount + 1;

				/*Get a unique name for the new node.*/
				do
				{
					newNodeName = Util.FormatResourceString("New_Account_Name", Util.Format(num));
					num++;

				} while (accounts.Contains(newNodeName));

				Account account = _curDatabase.CreateAccount(newNodeName);
				accounts.Add(account);

				TreeNode newNode = new TreeNode(newNodeName);
				newNode.Tag = account;
				nodes.Add(newNode);

				UpdateAccountCount(1);
				SetDatabaseChanged(true);
				
				/*Set the selected node after sorting
				 'cos the sort sets the selected node to null.*/
				tvAccounts.SelectedNode=( newNode);
			}
		}

		private void btnAccountRemove_Click(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, btnAccountRemove));

			if (_curPhase == Phase.DatabaseOpened)
			{
				TreeNode curNode = tvAccounts.SelectedNode;

				/*curnode shouldn't be null, but just in case..*/
				if (curNode == null)
				{
					return;
				}

				Account curAccount = (Account) curNode.Tag;

				if (Ask(Util.FormatResourceString("Remove_Account", curAccount.Name)))
				{
					/*Discard any changes made to the current acount.*/
					SetAccountChanged(false);

					/*Notify database modified.*/
					SetDatabaseChanged(true);

					UpdateAccountCount(-GetTotalCount(curAccount));

					curAccount.Remove();

					/*Calling 'Remove' will trigger 'BeforeSelect' and 'AfterSelect' events,
					 but, if the node count after removing becomes zero,
					 the events won't be triggered.*/
					curNode.Remove();

					if (tvAccounts.Nodes.Count == 0)
					{
						/*Have to manually trigger the 'AfterSelect' event.*/
						tvAccounts_AfterSelect(tvAccounts, new TreeViewEventArgs(null, TreeViewAction.ByMouse));
					}
				}
			}
		}

		private void tbpPassword_Enter(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tbpPassword));

			if (_curPhase == Phase.DatabaseOpened)
			{
				this.AcceptButton = btnChangePassword;
			}
		}

		private void tbpPassword_Leave(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tbpPassword));

			if (_curPhase == Phase.DatabaseOpened)
			{
				this.AcceptButton = null;
			}
		}

		private void btnChangePassword_Click(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, btnChangePassword));

			if (_curPhase == Phase.DatabaseOpened)
			{
				string p1 = txtChangePassword1.Text;
				string p2 = txtChangePassword2.Text;

				if (!p1.Equals(p2, StringComparison.Ordinal))
				{
					ShowInfoMessage(Util.GetResourceString("Passwords_Mismatch"));
					txtChangePassword1.SelectAll();
					txtChangePassword1.Focus();
					return;
				}

				if (!IsValidDatabasePassword(p1))
				{
					ShowInfoMessage(Util.GetResourceString("Enter_Password"));
					txtChangePassword1.SelectAll();
					txtChangePassword1.Focus();
					return;
				}

				_curDatabase.SetPassword(p1);

				ShowInfoMessage(Util.GetResourceString("Password_Changed"));
				txtChangePassword1.Text = string.Empty;
				txtChangePassword2.Text = string.Empty;
				SetDatabaseChanged(true);
			}
		}

		#endregion

		#region Toolbar button events handlers and methods

		private void EnableButtons(Buttons buttons, bool enable)
		{/*done*/
			if ((buttons & Buttons.New) == Buttons.New)
			{
				tsbNew.Enabled = enable;
			}
			if ((buttons & Buttons.Open) == Buttons.Open)
			{
				tsbOpen.Enabled = enable;
			}
			if ((buttons & Buttons.Close) == Buttons.Close)
			{
				tsbClose.Enabled = enable;
			}
			if ((buttons & Buttons.Save) == Buttons.Save)
			{
				tsbSave.Enabled = enable;
			}
			if ((buttons & Buttons.Apply) == Buttons.Apply)
			{
				tsbApply.Enabled = enable;
			}

			if ((buttons & Buttons.SetDefault) == Buttons.SetDefault)
			{
				tsbSetDefault.Enabled = enable;
				
				if (enable && IsCurrentFileDefault())
				{
					tsbSetDefault.Text = Util.GetResourceString("Remove_Default_Label");
					tsbSetDefault.ToolTipText = Util.GetResourceString("Tooltip_Remove_Default");
				}
				else
				{
					tsbSetDefault.Text = Util.GetResourceString("Set_Default_Label");
					tsbSetDefault.ToolTipText = Util.GetResourceString("Tooltip_Set_Default");
				}
			}
		}

		private bool IsCurrentFileDefault()
		{
			if (string.IsNullOrEmpty(_curFullPath))
			{
				return false;
			}

			string defaultFullPath = _settings.DefaultDatabaseFullPath;

			if (string.IsNullOrEmpty(defaultFullPath))
			{
				return false;
			}

			string fullPath = NormalizePath(_curFullPath);

			return !string.IsNullOrEmpty(fullPath) &&
				fullPath.Equals(defaultFullPath, StringComparison.InvariantCultureIgnoreCase);
		}

		private void tsbNew_Click(object sender, EventArgs e)
		{/*done*/

			Debug.Assert(object.ReferenceEquals(sender, tsbNew));

			if (!CheckForChanges())
			{
				return;
			}
			/*Close the file first.*/
			if (!CloseDatabase())
			{
				return;
			}
			ShowPanel(Panels.NewPassword, Phase.NewPassword);
		}

		private void tsbOpen_Click(object sender, EventArgs e)
		{/*done*/
			Debug.Assert(object.ReferenceEquals(sender, tsbOpen));

			/*Close the file first.*/
			if (!CloseDatabase())
			{
				return;
			}

			string fileName = BrowseForOpen();

			if (fileName == null)
			{
				return;
			}
			else
			{
			
				Open(fileName);
			}
		}

		private void tsbSave_Click(object sender, EventArgs e)
		{/*done*/
			Debug.Assert(object.ReferenceEquals(sender, tsbSave));

			if (_curPhase == Phase.DatabaseOpened)
			{
				/*Check for any account changes and validate the new account data.*/
				if (ApplyAccountChanges())
				{
					SaveDatabase();
				}
			}
		}

		private void tsbClose_Click(object sender, EventArgs e)
		{/*done*/
			Debug.Assert(object.ReferenceEquals(sender, tsbClose));

			CloseDatabase();
		}

		private void tsbApply_Click(object sender, EventArgs e)
		{/*done*/
			Debug.Assert(object.ReferenceEquals(sender, tsbApply));

			if (_curPhase == Phase.DatabaseOpened)
			{
				ApplyAccountChanges();
			}
		}

		private string NormalizePath(string path)
		{
			try
			{
				/* Hopefully this can expand and normalize the path 
				 (like removing ../ and ./ and resolving symbolic links. )*/
				return Path.GetFullPath(path);
			}
			catch (Exception e)
			{
				ExceptionBox.Show(
					this,
					Util.GetResourceString("Cannot_Set_Default"),
					e
				);
				return null;
			}
		}

		private void tsbSetDefault_Click(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tsbSetDefault));

			if (_curPhase == Phase.DatabaseOpened)
			{
				if (string.IsNullOrEmpty(_curFullPath))
				{
					this.ShowInfoMessage(Util.GetResourceString("Save_Database_First"));
					return;
				}

				if (IsCurrentFileDefault())
				{
					/*If current file is already default, then don't set it as default.*/
					
					string def = _settings.DefaultDatabaseFileName;
					_settings.DefaultDatabaseFileName = "";
					this.ShowInfoMessage(
						Util.FormatResourceString(
							"Default_Removed",
							def
						)
					);
					
				}
				else
				{
					string fullPath = NormalizePath(_curFullPath);
					string fullAppPath = NormalizePath(Application.ExecutablePath);

					if (fullPath == null || fullAppPath == null)
					{
						return;
					}

					fullAppPath = Path.GetDirectoryName(fullAppPath);

					/* checks whether the current database file resides in the
					 * same folder as the executable file.*/
					if (_curFullPath.StartsWith(fullAppPath, StringComparison.InvariantCultureIgnoreCase))
					{
						_settings.DefaultDatabaseFileName = Path.GetFileName(_curFullPath);
					}
					else
					{
						_settings.DefaultDatabaseFileName = _curFullPath;
					}

					this.ShowInfoMessage(Util.FormatResourceString("Default_Set", _settings.DefaultDatabaseFileName));
				}

				/* Update the button label text. */
				EnableButtons(Buttons.SetDefault, true);
			}
		}

		#endregion

		#region pnlNewPassword event handlers

		private void btnNewPasswordOK_Click(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, btnNewPasswordOK));

			if (_curPhase == Phase.NewPassword)
			{

				string p1 = txtNewPassword1.Text;
				string p2 = txtNewPassword2.Text;

				if (!p1.Equals(p2, StringComparison.Ordinal))
				{
					ShowInfoMessage(Util.GetResourceString("Passwords_Mismatch"));
					txtNewPassword1.SelectAll();
					txtNewPassword1.Focus();
					return;
				}

				if (!IsValidDatabasePassword(p1))
				{
					ShowInfoMessage(Util.GetResourceString("Enter_Password"));
					txtNewPassword1.SelectAll();
					txtNewPassword1.Focus();
					return;
				}

				Dispose(_curDatabase);
				_curDatabase = new Database(p1);
				txtNewPassword1.Text =
					txtNewPassword2.Text = string.Empty;

				DisplayDatabase();
			}
		}

		private void btnNewPasswordCancel_Click(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, btnNewPasswordCancel));

			if (_curPhase == Phase.NewPassword)
			{
				CloseDatabase();
			}

		}
		#endregion

		#region Drap n Drop Support

		private bool RemoveHighlight(bool compare,TreeNode destNode)
		{
			if (tvAccounts.Tag != null)
			{
				object[] objArray = (object[]) tvAccounts.Tag;
				TreeNode prevDestNode = (TreeNode) objArray[0];

				if (compare && object.ReferenceEquals(prevDestNode, destNode))
				{
					
					return false;
				}

				if (prevDestNode != null)
				{
					prevDestNode.BackColor = (Color) objArray[1];
				}
				else
				{
					tvAccounts.BackColor = (Color) objArray[1];
				}

				tvAccounts.Tag = null;

			}
			
			return true;
		}

		private void tvAccounts_ItemDrag(object sender, ItemDragEventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tvAccounts));

			tvAccounts.SelectedNode = (TreeNode) e.Item;

			if (!_isCurAccountChanged )
			{
				tvAccounts.Tag = null;
				tvAccounts.DoDragDrop(e.Item, DragDropEffects.Move);
				return;
			}
		}

		private void tvAccounts_DragEnter(object sender, DragEventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tvAccounts));

			e.Effect = DragDropEffects.None;
			
			if (e.Data != null && e.Data.GetDataPresent(typeof(TreeNode)))
			{
				TreeNode srcNode = (TreeNode) e.Data.GetData(typeof(TreeNode));

				/*Make sure the source node comes from our tree view.*/
				if (srcNode != null && object.ReferenceEquals(srcNode.TreeView, tvAccounts))
				{
					e.Effect = DragDropEffects.Move;
				}
			}
		}

		/// <summary>
		/// Tests whether node1 is an ancestor/parent of node2
		/// </summary>
		/// <param name="node1"></param>
		/// <param name="node2"></param>
		/// <returns></returns>
		private bool IsAncestor(TreeNode node1, TreeNode node2)
		{
			if (node2 == null || node1==null)
			{
				return false;
			}

			TreeNode parent = node2.Parent;

			while (parent != null)
			{
				if (object.ReferenceEquals(parent, node1))
				{
					return true;
				}

				parent = parent.Parent;
			}

			return false;
		}

		/// <summary>
		/// Checks whether a source node is allowed to be dropped on a specified
		/// destination node.
		/// </summary>
		/// <param name="srcNode"></param>
		/// <param name="destNode"></param>
		/// <returns></returns>
		private bool IsSuitableTarget(TreeNode srcNode, TreeNode destNode)
		{
			if (srcNode == null)
			{
				throw new ArgumentNullException("srcNode");
			}

			/*Note that 'destNode' can be null, in which case it refers to
			 the treeview control itself.*/

			if (
				object.ReferenceEquals(srcNode, destNode) ||

				/*Make sure 'srcNode' does not become a child of its child.*/
				IsAncestor(srcNode, destNode) ||

				/*Make sure 'srcNode' comes from our own treeview.*/
				!object.ReferenceEquals(srcNode.TreeView, tvAccounts)
			)
			{
				return false;
			}

			/**
			 * Now check whether 'destNode' already contains another
			 * account with the same name as that of 'srcNode'.
			 * */
			
			Account srcAccount = (Account) srcNode.Tag;

			if (destNode == null)
			{
				return !_curDatabase.Accounts.Contains(srcAccount.Name);
			}

			Account destAccount = (Account) destNode.Tag;
			return !destAccount.Accounts.Contains(srcAccount.Name);
		}

		private TreeNode GetNodeAt(DragEventArgs e)
		{
			Point pt = tvAccounts.PointToClient(new Point(e.X, e.Y));

			/*Note that 'destNode' may be null, in which case it refers
			 to the treeview control itself.*/
			TreeNode destNode = tvAccounts.GetNodeAt(pt);

			/*Make sure the location of the mouse cursor is strictly
			  inside the bounds of 'destNode'.*/
			if (destNode != null && !destNode.Bounds.Contains(pt))
			{
				return null;
			}

			return destNode;
		}

		private void tvAccounts_DragDrop(object sender, DragEventArgs e)
		{
			/*We reach here when the user has release the mouse button.*/

			Debug.Assert(object.ReferenceEquals(sender, tvAccounts));

			if (e.Data.GetDataPresent(typeof(TreeNode)))
			{
				/*Note that 'destNode' may be null, in which case it refers
				 to the treeview control itself.*/
				TreeNode destNode = GetNodeAt(e);
				TreeNode srcNode = (TreeNode) e.Data.GetData(typeof(TreeNode));

				Debug.Assert(srcNode != null);

				RemoveHighlight(false, null);

				if (IsSuitableTarget(srcNode,destNode))
				{
					Account srcAccount = (Account) srcNode.Tag;
					string name = srcAccount.Name;

					/*Must remove first before adding.*/
					srcNode.Remove();
					srcAccount.Remove();

					if (destNode != null)
					{
						destNode.Nodes.Add(srcNode);
						destNode.Expand();

						Account destAccount = (Account) destNode.Tag;
						destAccount.Accounts.Add(srcAccount);
					}
					else
					{
						/*Add to root.*/
						tvAccounts.Nodes.Add(srcNode);
						_curDatabase.Accounts.Add(srcAccount);
					}

					tvAccounts.SelectedNode = srcNode;
					SetDatabaseChanged(true);
				}
			}
		}

		private void tvAccounts_DragOver(object sender, DragEventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tvAccounts));

			/*This event handler mainly provides hightling of dest node.*/

			if (e.Data.GetDataPresent(typeof(TreeNode)))
			{
				/*Note that 'destNode' may be null, in which case it refers
				 to the treeview control itself.*/
				TreeNode destNode = GetNodeAt(e);
				TreeNode srcNode = (TreeNode) e.Data.GetData(typeof(TreeNode));

				Debug.Assert(srcNode != null);

				if (!RemoveHighlight(true, destNode))
				{
					/*Will return if previous 'destNode' is the same
					 as current 'destNode'.*/
					return;
				}

				//if (
				//    /*Don't hightlight selected node.*/
				//    !object.ReferenceEquals(destNode, tvAccounts.SelectedNode) 
				//)
				//{
					/*We will highlight the dragged over node no matter
					 whether it is suitable as a target.*/

					object[] objArray = new object[2];
					if (destNode != null)
					{
						objArray[0] = destNode;
						objArray[1] = destNode.BackColor;
						destNode.BackColor = SystemColors.ButtonFace;

						/*Provides scrolling effect when the mouse is
						 dragging over the last visible node in the treeview control.*/
						if (destNode.NextNode != null)
						{
							destNode.NextNode.EnsureVisible();
						}
						if (destNode.PrevNode != null)
						{
							destNode.PrevNode.EnsureVisible();
						}
						
						destNode.Expand();
					}
					else
					{
						/*The treeview is selected as the 'destNode'.*/
						objArray[0] = null;
						objArray[1] = tvAccounts.BackColor;
						tvAccounts.BackColor = SystemColors.ButtonFace;
					}

					tvAccounts.Tag = objArray;
				//}
				
				if (IsSuitableTarget(srcNode, destNode))
				{
					e.Effect = DragDropEffects.Move;
				}
				else
				{
					/*Indicate to the user that he is not allowed to drop
					 the 'srcNode' on the 'destNode'.*/
					e.Effect = DragDropEffects.None;
				}
			}
		}

		private void tvAccounts_DragLeave(object sender, EventArgs e)
		{
			Debug.Assert(object.ReferenceEquals(sender, tvAccounts));

			RemoveHighlight(false, null);
		}

		#endregion

		private void tsbAbout_Click(object sender, EventArgs e)
		{
			this.ShowInfoMessage(
				"PassKeeper 1.1.0.0\n"+
				"Copyright 2008 Lucas Tan\n\n"+
				"If you find this program useful, pass it on to your friends!"
				);
		}
	}
}